-
Notifications
You must be signed in to change notification settings - Fork 348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add :telemetry_prefix option to Tesla.Middleware.Telemetry #390
Conversation
lib/tesla/middleware/telemetry.ex
Outdated
You can configure `:telemetry_prefix` globally in your config, but if set the `:telemetry_prefix` option will override: | ||
|
||
``` | ||
config :tesla, Tesla.Middleware.Telemetry, telemetry_prefix: [:custom, :prefix] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is a good idea - it would override this all clients from all dependencies.
Since the use case comes from building API clients as packages I think it would be better to hide tesla from end user and use this instead.
# config/config.exs
config :myclient, telemetry_prefix: [...]
# myclient.ex
defmodule Myclient do
use Tesla
@compile_config Application.compile_env(:myclient, :telemetry_prefix, [])
plug Tesla.Middleware.Telemetry, prefix: Keyword.get(@compile_config, :prefix, [:myclient])
# ...
end
# or top provide runtime configuration
defmodule Myclient do
def new do
config = Application.get_env(:myclient, :telemetry_prefix, [])
middleware = [
# ...
{Tesla.Middleware.Telemetry, prefix: Keyword.get(@compile_config, :prefix, [:myclient])}
]
Tesla.client(middleware, adapter)
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I kept going back and forth on globalizing. Will remove and update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just updated. Let me know if that works.
lib/tesla/middleware/telemetry.ex
Outdated
@@ -18,6 +18,34 @@ if Code.ensure_loaded?(:telemetry) do | |||
end) | |||
``` | |||
|
|||
## Options | |||
- `:telemetry_prefix` - replaces default `[:tesla]` with desired Telemetry event prefix (see below) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this option is passed directly to telemetry middleware I don't think we need to include the telemetry_
prefix in the prefix name (🥁)
I'd go with just :prefix
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Will update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just updated. Let me know if that works.
@keatz55 Thanks for the PR! I've added a few comments on the diff. @bryannaegele Would you mind taking a look? :) |
❤️ |
Sure, @teamon ! @keatz55 can you elaborate on what your use case is and what you're looking to accomplish? The telemetry team and Observability WG have established telemetry prefixes in libraries as an anti-pattern since the primary use case of distinguishing different usage contexts of a library can be accomplished through event metadata. We'd love to walk that pattern back, but we're stuck with it in a couple of places. :/ I'm happy to provide suggestions or guidance to help accomplish your goal, but I do not believe this PR should be merged. I would suggest opening an issue instead and find a solution for you that could possibly be documented better. We did have a use case at WM that was somewhat related but is solvable through different middleware. |
@bryannaegele At least in our case, the use case is to be able to track metrics from connections to different services seperately. In our app, we have tesla clients connection to at least three external services and currently the telemetry metrics that come in have no way of distinguishing between which service was being called. I had roughed out an alternative solution here: #387. Do you think that approach works better? |
@aselder yeah! So we had a similar thing internally where they wanted to use something other than the module name in the Example: # middleware
defmodule MyApp.Tesla.Middleware.Metadata do
def call(env, next, opts) do
env_opts = Keyword.merge(env.opts, opts)
env = %{env | opts: env_opts}
Tesla.run(env, next)
end
end
# calling
middleware = [
{MyApp.Tesla.Middleware.Metadata, service: "target_service"},
Tesla.Middleware.Telemetry
]
# metric
counter("tesla_request_total",
event_name: [:tesla, :request, :stop],
description: "Tesla http request count",
tags: [:module, :method, :status],
tag_values: fn %{env: env} ->
# Get the service name given to the metadata middleware keyword list
target_service = env.opts[:service]
%{
method: env.method,
status: env.status,
target_service: target_service
}
end) So there's certainly a pattern that could possibly be formally established as additional middleware rather than altering the Tesla client code or adding prefixes to telemetry events. The added bonus with going the extra middleware route is that it makes that metadata available for other use cases, such as the logger middleware. I encouraged @moxley to formalize this pattern into a generic metadata middleware that could go in here. Would anyone be interested in taking up a proposal for that and example use cases with the telemetry and logger middlewares? @teamon any thoughts? Note that it would probably be better to house this under a metadata key in the opts rather than blending them together. |
@bryannaegele I like that approach. It's close to what I had done, just a much cleaner way of injecting it. I'd like to propose however that a metadata field be added to the Env, instead of using opts. From the name of the middleware, it's not clear that it will be storing passed information in the |
Thanks for the response @bryannaegele,
We have the same internal case mentioned above by @aselder with the addition of the following cases:
As a side note, when I submitted the above PR I was using a similar approach to the ecto telemetry implementation where you have a specific repo and telemetry events are based on the repo module name: |
I'm not sure if you noticed, but there is also built-in Before telemetry was a thing I've built an internal integration for tesla & prometheus. From observability perspective it makes sense to treat One of the design principles of tesla was to let users choose their http adapters. With this in mind, if you are creating an API wrapper library you could allow users to use a different adapter. With telemetry as an abstraction/integration layer it make sense to use the same event handler for multiple clients as the metrics represent the same thing, and if one wants to customise the handler for specific client it can be done by looking into tags. As for the logger - the current one is far form perfect but I have yet to find time to propose a better solution. tl;dr - I'm not sure, the simpler and more practical the better |
@teamon Nice call... Didn't even realize |
Yes, unfortunately that is the anti-pattern I was denoting. We can't remove it, so we're stuck with it in that library, but we highly discourage this practice going forward.
In this case, we'd suggest using your own events and wrapping the calls. In a sense, the use of prefixes was a way to "hijack" a library's events. This also corners you and your users into using Tesla since those events would always be a part of your events. The usage of Tesla is an implementation concern of your SDK, so bleeding that out can be problematic.
In practice this is the inverse since An additional issue is that with prefixes our potential solutions for event discovery for tracing won't work. Our solutions for exposing traceable spans via telemetry events is highly dependent on static definition of events and that they be consistent for all uses of the library. Prefixes require the use of macros and still don't solve the problem of distinguishing individual requests from a particular client. If you'd like to read more about how some of these conclusions were reached, especially around events in libraries, you can check out elixir-plug/plug_cowboy/pull/25, elixir-plug/plug_cowboy/pull/31, and #347 Edit: clarifying point on the Edit #2: Additional discussion on prefixes beam-telemetry/telemetry_metrics/issues/55 Edit #3: Agenda item and resolution can be found in our meeting minutes for April 9 https://docs.google.com/document/d/18JVh6ICLyRCJBpRVIXwcR1fb-K4qRX3WHYgtM7mFx2U/edit |
One change I'd like to ask as part of this is to pass the env along for the exception event. If we're relying on |
@bryannaegele Thank you for taking the time for such thorough response and for including sources. It is much appreciated. Under the additional context that you have just given, would it make sense to remove the |
@keatz55 no problem. We still want Say we use that pattern in multiple libraries, such as http clients or servers, such as Phoenix. Was it an http request stopping? And was it Phoenix? Or was it an http client? We quickly find ourselves in a precarious situation because in each of these cases, the measurements and metadata being emitted are different causing things like Metrics to no longer work. So we have to distinguish which library it came from, which is the reasoning for libraries to have a root prefix for all of its events. I apologize for all of the confusion. A lot of people have put out some helpful information but we need to get official guides together. Two other approaches to this particular issue should be put out there, one of which could be helpful for some use cases presented here.
|
@bryannaegele Thanks again. Everything you are saying makes perfect sense. One thing I still don't quite understand is why in the following sample in elixir-plug/plug_cowboy/pull/31 they don't use the
|
@keatz55 That's a great callout. I don't know that we put a lot more thought into it beyond it being a The main theme is consistency across the entire ecosystem, so we should provide docs with guidance. These patterns, conventions, and libraries are born out of trial and error, which has been rough for the community, but is necessary to determine the best overall solutions. How do you feel about the ability to pass static metadata as an option? Do you want to take a crack at that? |
Sidenote, just to highlight the customisation options in tesla: # 1. Pass static opts
defmodule MyStaticClient do
use Tesla
plug Tesla.Middleware.Telemetry, static: opts
end
# 2. Pass opts on client creation
defmodule MyDynamicClient do
def new(opts) do
middleware = [
# ...
{Tesla.Middleware.Telemetry, [dynamic: opts]}
]
end
end
# 3. Wrap with custom middleware and modify on each request
defmodule MyTelemetryMiddleware do
def call(env, next, opts) do
{new_env, new_opts} = do_magic_with(env, opts)
Tesla.Middleware.Telemetry.call(new_env, next, new_opts)
end
end |
@bryannaegele Sounds good. I'll take a crack at it tonight post-work. |
👋 @keatz55 How's going? :) |
@teamon I apologize. Work has been eating me alive and I completely spaced this. I'll take a crack at it this weekend. |
@bryannaegele Is the convention described in https://keathley.io/blog/telemetry-conventions.html blessed by beam-telemetry community? :) |
@teamon generally, yes. I was supposed to review it prior to publication but he pretty much nailed it.
|
Let me know what do you think of #419 |
This PR gives Tesla HTTP client users the ability to configure a
:telemetry_prefix
option at theTesla.Middleware.Telemetry
module orconfig.ex
level. This is useful for developers creating platform-specific sdks or api clients that want Telemetry events to align more closely with their platform or app.